package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"math"
	"math/rand"
	"net/http"
	"regexp"
	"strconv"
	"time"
)

type Candidate struct {
	Id      string
	Name    string
	IsHuman bool
	BotType int
}

type Player struct {
	Candidate
	Card    []string
	Chips   int
	Ante    int
	Invest  int // 投入
	Profit  int // 收益
	HasQuit bool
	HasFold bool

	Timeout    int // 等待玩家动作的时间
	TimeoutMax int

	action chan Event
	choice chan string
}

func (p *Player) GetId() string {
	return p.Id
}

func (p *Player) DoAnte(game *Game) int { //底注

	if p.HasQuit {
		// 该玩家已离场
		log.Println("ERROR: 该玩家已离场", p.Id, p.Name)
		return -1
	}

	ante := game.MinAmount
	if p.Chips < ante {
		// 钱不够，强制离场(ante=0)
		return 0
	}

	if p.IsHuman {
		// 人类玩家：选择 1下底注 2离开(leave)。超时没有选择默认离开(ante=0)
		timeout := make(chan int)
		due := 60

		go func() {
			for {
				time.Sleep(1e9)
				due--
				p.Timeout = due
				// fmt.Println("底注倒计时", due)
				if due <= 0 {
					break
				}
			}

			if due == 0 {
				// Timeout正常结束，而不是强制结束(due=-1)
				log.Println("[Player] Ante Timeout: ", p.Id, game.Id)
				timeout <- 0
			}
		}()

		select {
		case c := <-p.choice:
			log.Println("[Player] Ante Choice: ", p.Id, game.Id, c)
			if c == "leave" {
				ante = 0
			}
			due = -1 // 强制结束Timeout

		case <-timeout:
			log.Println("[Player] Timeout: ", p.Id, game.Id)
			ante = 0
		}

	}

	return ante

}

func (p *Player) YourTurn(game *Game) { //下注

	if p.HasFold {
		game.event <- Event{p.Id, 0, ""}
		return
	}

	if p.IsHuman {
		// wait for human action until timeout
		timeout := make(chan int)
		due := 30

		go func() {
			for {
				time.Sleep(1e9)
				due--
				p.Timeout = due
				if due <= 0 {
					break
				}
			}

			if due == 0 {
				// Timeout正常结束，而不是强制结束(due=-1)
				log.Println("[Player] Timeout: ", p.Id, game.Id)
				timeout <- 0
			}
		}()

		select {
		case e := <-p.action:
			log.Println("[Player] Action: ", p.Id, game.Id, e)
			game.event <- e
			due = -1 // 强制结束Timeout
		case <-timeout:
			log.Println("[Player] Timeout: ", p.Id, game.Id)
			game.event <- Event{p.Id, 1, "timeout"}
		}

	} else {
		//desc := p.game.Describe(p.Id)

		if p.BotType == 1 {
			// 基于大模型的算法 LLM 1
			bet, reason := p.Strategy1(game)
			game.event <- Event{p.Id, bet, reason}
		} else {
			// 基于规则的下注策略 (p.BotType==0)
			time.Sleep(2 * 1e9)
			bet, reason := p.Strategy0(game)
			game.event <- Event{p.Id, bet, reason}
		}
	}

}

func (p *Player) Strategy0(game *Game) (int, string) { //下注策略1

	r := 1.0 - ratio(p.Card) // 手牌的大小

	// 获胜的期望
	// 在一个N人游戏中，当前奖池有A筹码，最小下注额B筹码。
	// 如果获利期望超过50%，则跟注

	A := float64(game.PrizePool)
	B := float64(game.MinAmount)
	C := float64(p.Chips - p.Invest) // 投注后剩余筹码
	N := float64(game.NumWinners)

	P := math.Pow(r, N)

	T := len(game.Records) / len(game.Players) // 轮次 turn

	F := 0.96           // 信心指数
	X := rand.Float64() // 冒险系数
	f := math.Pow(F, float64(T))

	remark := "\n"
	remark += fmt.Sprintf("玩家%s,轮次%d\n", p.Name, T)
	remark += fmt.Sprintf("手牌%s, %.3f\n", p.Card, r)
	remark += fmt.Sprintf("胜率%.2f，信心%.2f，冒险%.2f\n", P, f, X)

	// remark += fmt.Sprintf("玩家数%d/%d\n", game.NumWinners, len(game.Players))
	// remark += fmt.Sprintf("筹码%d,最低下注%d,奖池%d\n", p.Chips, game.MinAmount, game.PrizePool)

	// E := P*A + (1-P)*B

	/*
	   要在一个牌局游戏中持续获利，可以考虑以下因素：
	   手牌的胜率：评估自己手中的牌的强度和胜率，根据胜率来决定是否继续下注或者加注。如果手牌强大，胜率高，可以选择加注或者继续下注，以增加奖池和获得更高的赢利机会。如果手牌较弱，胜率低，可以选择弃牌，以避免损失。
	   玩家数量：考虑牌局中的玩家数量，尤其是对手的数量。如果玩家数量较多，可能意味着竞争更加激烈，获利的机会也相对较小。此时可以采取更保守的策略，选择更强的手牌才下注或加注。如果玩家数量较少，获利机会可能更大，可以更主动地进行下注或加注。
	   奖池：奖池的大小直接影响了获利的潜力。如果奖池较大，可以有更多的赢利机会，可以考虑更主动地下注或加注，以争取获得更高的赢利。如果奖池较小，可能需要更保守地进行下注或加注，以避免过大的风险。
	   最小下注额：最小下注额是一个重要的参考指标，可以根据自己的筹码数量和手牌的胜率来决定是否下注。如果最小下注额过高，可能需要更强的手牌才能下注或加注。如果最小下注额较低，可以更容易地进行下注或加注，以增加奖池和获得更高的赢利机会。
	   筹码数量：筹码数量直接关系到获利的潜力和风险承受能力。如果拥有较多的筹码，可以更主动地进行下注或加注，以争取更高的赢利。如果筹码数量较少，需要更谨慎地进行下注，避免过大的风险。在游戏中要合理管理筹码，控制下注的大小，以保持持续的获利能力。
	   综上所述，持续获利需要综合考虑手牌的胜率、玩家数量、奖池、最小下注额和筹码数量等因素，根据具体情况制定合理的下注策略，灵活应对不同的牌局局势。
	*/

	/*
		在一个N人轮流下注游戏中，每个对手的牌大于我的手上的牌的概率是R。目前奖池有A，最小下注B。我有C筹码，如果筹码耗尽则会出局。
		拟采用的下注策略：
			1. 牌面大小：如果牌面大，不轻易加注，为了引诱更多人跟注，增加奖池；如果牌面小，在某些场合可能加注，迷惑对手
			2. 筹码数量：如果筹码多，下注风险小，采取激进策略；如果筹码较少，采取保守策略
			3. 奖池：如果奖池较大，倾向于跟注

		为了使获利的期望最大，请给出一个函数，计算我下注的最佳值。-1表示弃牌。
		并给出全部推导过程。

	*/

	if P*f > 0.6-0.2*X {
		// 如果胜率足够大(50%)，确定下注
		remark += fmt.Sprintf("Action: 确定下注：%.2f > %.2f \n", P*f, 0.7-0.2*X)

		//加注策略
		risk := 0.0

		if C/B > 10*N {
			// 筹码充足：剩余筹码至少能跟注10轮
			remark += fmt.Sprintf("**筹码充足：%d / %d\n", p.Chips, game.MinAmount)

			if A/B > 20 {
				// 奖池回报足够高
				remark += fmt.Sprintf("**奖池回报足够高：%d / %d\n", game.PrizePool, game.MinAmount)

				threshold := 0.9 + 0.1*X
				if r > threshold || X > 0.95 {
					remark += fmt.Sprintf("Action：加注\n")

					// 牌面足够大（胜率90~100%）
					remark += fmt.Sprintf("**牌面条件：%.2f > 阈值：%.2f\n", r, threshold)

					// 临时起意加注（欺骗，引入随机因素）
					remark += fmt.Sprintf("**冒险因子：%.2f\n", X)

					risk = B
				}
			}
		}

		x := B + risk // 倾向于最小下注减少风险。根据上述因素调整风险项
		bet := min(max(x, B), C)

		return int(bet), remark

	} else {

		threshold := 0.5 + 0.2*X // 跟注阈值

		if r*f > threshold {
			// 牌面较大，值得尝试跟注；
			// 随着轮次的增加，信心逐渐减弱
			remark += fmt.Sprintf("Action：尝试跟注\n")

			// 或者牌面相对较大20~40%
			remark += fmt.Sprintf("**牌面条件：%.2f vs 动态阈值%.2f\n", r, threshold)

			// 如果筹码充足
			if C/B > 5*N {
				remark += fmt.Sprintf("**筹码充足：%d / %d\n", p.Chips, game.MinAmount)
				return game.MinAmount, remark
			}

			remark += "Action：选择不跟注\n"
			return -1, remark

		} else {

			remark += "Action：选择弃牌\n"
			return -1, remark
		}
	}
}

// 从大模型中提取Action
func GetModelAction(str string) int {
	// 模型的输出格式: LLM_ACTION=20
	re := regexp.MustCompile(`LLM_ACTION=([-]*[0-9-]+)`)
	match := re.FindStringSubmatch(str)
	action := -1
	if len(match) > 1 {
		i, err := strconv.Atoi(match[1])
		if err == nil {
			action = i
		}
	}

	return action
}

// 下注策略: LLM-1
func (p *Player) Strategy1(game *Game) (int, string) {

	jsonValue, err := json.Marshal(game)
	if err != nil {
		fmt.Println("JSON encoding error:", err)
		return -1, "JSON encoding error"
	}

	reader := bytes.NewReader(jsonValue)

	url := "http://10.132.66.8:32343/api/chat/transGameParams"
	resp, err := http.Post(url, "application/json", reader)
	if err != nil {
		fmt.Println("HTTP POST error:", err)
		return -1, "HTTP POST error"
	}
	defer resp.Body.Close()

	if resp.StatusCode == http.StatusOK {
		fmt.Println("POST request successful")
	} else {
		fmt.Println("POST request failed")
	}

	responseString := ""
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("Response reading error:", err)
		return -1, responseString

	} else {
		responseString = string(body)
		bet := GetModelAction(responseString)
		return bet, responseString
	}

}
